package com.ziroom.crm.mq.core.retry;

import com.ziroom.crm.mq.core.data.Message;
import com.ziroom.crm.mq.core.retry.delay.FixedDelayPolicy;
import com.ziroom.crm.mq.core.retry.policy.CountRetryPolicy;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 默认的重试处理器
 * <p/>
 *
 * @author WeiShaoying
 * @date Created in 2020年09月18日 11:05
 * @since 1.0.0
 */
@NoArgsConstructor
@Slf4j
@SuppressWarnings("ALL")
public class DefaultRetryHandler implements RetryHandler {

    private volatile DelayPolicy delayPolicy = new FixedDelayPolicy();

    private volatile RetryPolicy retryPolicy = new CountRetryPolicy();

    private final List<RetryFinishCallBack> retryFinishCallBackList = RetryFinishCallBackFactory.getRetryFailCallBackList();

    public DefaultRetryHandler(RetryPolicy retryPolicy, DelayPolicy delayPolicy) {
        this.retryPolicy = retryPolicy;
        this.delayPolicy = delayPolicy;
    }

    //SimpleAsyncTaskExecutor-1
    @Override
    public void retry(RetryFunction retryFunction, RetryContext context) {
        if (Objects.isNull(context) || Objects.isNull(retryFunction)) {
            return;
        }
        RetryContext retryContext = retryPolicy.open(context);
        DelayContext delayContext = delayPolicy.start(context);

        while (canRetry(retryPolicy, retryContext) && !retryContext.isStopRetry()) {
            try {
                log.info("Retry: 第" + retryContext.getRetryCount() + "次");
                retryFunction.retry(retryContext);
                // 成功时
                retryContext.getMessage().setException(null);
                break;
            } catch (Exception e) {
                // 注册异常
                retryPolicy.dealException(retryContext, e);

                // 延迟处理
                if (RetryType.IMMEDIATELY == retryContext.getRetryType()) {
                    // 内存延迟
                    delayImmediately(retryContext, delayContext);
                } else if (RetryType.PERSISTENCE == retryContext.getRetryType()) {
                    // 持久化延迟,仅执行一次
                    delayPersistence(retryContext, delayContext);
                    break;
                }
            }
        }
        //重试完毕后
        runFinishCallBack(context);
    }

    /**
     * 跳过重试，仅处理异常（延时）部分逻辑
     *
     * @param retryContext 重试内容
     * @param e            异常
     */
    @Override
    public void handleException(RetryContext retryContext, Exception e) {
        // 注册异常，计算次数
        retryPolicy.dealException(retryContext, e);
        DelayContext delayContext = delayPolicy.start(retryContext);
        // 延迟处理
        if (RetryType.IMMEDIATELY == retryContext.getRetryType()) {
            // 内存延迟
            delayImmediately(retryContext, delayContext);
        } else if (RetryType.PERSISTENCE == retryContext.getRetryType()) {
            // 持久化延迟
            delayPersistence(retryContext, delayContext);
        }
        runFinishCallBack(retryContext);
    }

    /**
     * 内存重试，延迟处理
     * 1、立即执行延时处理
     * 2、延时后继续重试，直到重试策略完毕
     *
     * @param retryContext 重试内容
     * @param delayContext 延时消息体
     */
    private void delayImmediately(RetryContext retryContext, DelayContext delayContext) {
        if (canRetry(retryPolicy, retryContext) && !retryContext.isStopRetry()) {
            try {
                delayPolicy.delay(delayContext);
            } catch (Exception ex) {
                // back off was prevented by another thread - fail the retry
                if (log.isDebugEnabled()) {
                    log.debug("Abort retry because interrupted: count=" + retryContext.getRetryCount());
                }
            }
        } else {
            log.info("内存重试失败，将保存失败消息稍后继续重试，当前重试次数：{}次", retryContext.getRetryCount());
        }
    }


    /**
     * 持久化延迟处理
     * 1、设置下一次重试时间，用于持久化
     * 2、不执行延时策略，跳过当前环节（=每次仅重试1次，延时通过持久化处理）
     *
     * @param retryContext 重试内容
     * @param delayContext 延时消息体
     */
    private void delayPersistence(RetryContext retryContext, DelayContext delayContext) {
        // 当前异常以及处理，重试次数已经+1 = 当前的重试次数
        long delayTime = delayPolicy.getDelayTime(delayPolicy.start(retryContext));
        Message message = retryContext.getMessage();

        if (canRetry(retryPolicy, retryContext)) {
            // 设置持久化的message数据
            long nextTimeStamp = System.currentTimeMillis() + delayTime;
            Date nextTime = new Date(nextTimeStamp);
            message.setNextRetryDate(nextTime);
            log.info("消息{}的第{}次重试，下一次延时{} ms后执行", message.getId(), retryContext.getRetryCount(), delayTime);
        } else {
            // 标记
            message.setRetryFailed(true);
            log.info("消息{}的第{}次重试，已达到重试上限，将成为死信数据", message.getId(), retryContext.getRetryCount());
        }
    }

    protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
        return retryPolicy.canRetry(context);
    }

    /**
     * 重试失败回调，目前消费端、发送端重试失败后都会走这个方法
     *
     * @param retryContext 重试内容
     */
    protected void runFinishCallBack(RetryContext retryContext) {
        // 最终结果
        if (retryContext.getMessage().isRetryFailed()) {
            retryFail(retryContext);
        } else if (Objects.isNull(retryContext.getMessage().getException())) {
            retrySuccess(retryContext);
        }
    }

    /**
     * 重试失败
     *
     * @param retryContext
     */
    protected void retryFail(RetryContext retryContext) {
        //重试失败回调
        if (log.isDebugEnabled()) {
            log.debug("持久化重试失败:content=" + retryContext);
        }
        // 遍历所有的回调
        retryFinishCallBackList.forEach(failCallBack -> {
            try {
                failCallBack.fail(retryContext);
            } catch (Exception e) {
                log.error("重试失败后回调异常:content=" + retryContext, e);
            }
        });
    }

    /**
     * 重试成功
     *
     * @param retryContext
     */
    protected void retrySuccess(RetryContext retryContext) {
        if (log.isDebugEnabled()) {
            log.debug("持久化重试成功:content=" + retryContext);
        }
        // 遍历所有的回调
        retryFinishCallBackList.forEach(failCallBack -> {
            try {
                failCallBack.success(retryContext);
            } catch (Exception e) {
                log.error("重试成功后回调异常:content=" + retryContext, e);
            }
        });
    }
}
